package com.quick.develop.flowable.application.impl;

import cn.hutool.core.collection.CollUtil;
import com.quick.develop.flowable.constant.BpmConstants;
import com.quick.develop.flowable.domain.dto.task.BpmTaskDTO;
import com.quick.develop.flowable.domain.vo.instance.BpmProcessInstanceCancelReqVO;
import com.quick.develop.flowable.domain.vo.task.BpmAddMultiExtReqVO;
import com.quick.develop.flowable.domain.vo.task.BpmTaskRejectReqVO;
import com.quick.develop.flowable.domain.vo.task.BpmTaskReqVO;
import com.quick.develop.flowable.domain.vo.task.BpmTaskUpdateAssigneeReqVO;
import com.quick.develop.flowable.enums.BpmExecuteCmd;
import com.quick.develop.flowable.enums.BpmTaskCategory;
import com.quick.develop.framework.utils.StringUtils;
import com.quick.develop.framework.security.utils.SecurityUtils;
import com.quick.develop.flowable.application.IBpmProcessInstanceApplication;
import com.quick.develop.flowable.application.IBpmTaskApplication;
import com.quick.develop.flowable.application.IBpmTaskAssigneeApplication;
import com.quick.develop.flowable.framework.cmd.ClearTaskDescCmd;
import com.quick.develop.flowable.framework.cmd.CreateHistoricDataCmd;
import com.quick.develop.flowable.framework.cmd.SetTaskScopeTypeCmd;
import com.quick.develop.flowable.framework.cmd.WithdrawTaskCmd;
import com.quick.develop.flowable.framework.convert.task.BpmTaskConvert;
import com.quick.develop.flowable.framework.factory.FlowServiceFactory;
import com.quick.develop.flowable.utils.FlowableUtil;
import com.quick.develop.flowable.service.definition.IBpmProcessDefinitionService;
import com.quick.develop.flowable.service.process.IBpmProcessInstanceService;
import com.quick.develop.flowable.service.task.*;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableTaskAlreadyClaimedException;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.*;
import static com.quick.develop.framework.common.utils.ServiceExceptionUtils.exception;
import static com.quick.develop.framework.utils.collection.CollectionUtils.*;

/**
 * @author junyuan.liu
 * @description: 任务处理
 * @date 2022/5/5 16:22
 */
@Component
@Slf4j
public class BpmTaskApplicationImpl extends FlowServiceFactory implements IBpmTaskApplication {
    @Resource
    private IBpmPermissionService bpmPermissionService;

    @Resource
    private IBpmProcessDefinitionService bpmProcessDefinitionService;

    @Resource
    private IBpmProcessInstanceService bpmProcessInstanceService;

    @Resource
    private IBpmExecutionService bpmExecutionService;

    @Resource
    private IBpmActivityService bpmActivityService;

    @Resource
    private IBpmCommentService bpmCommentService;

    @Resource
    private IBpmTaskService bpmTaskService;

    @Resource
    private IBpmTaskAssigneeApplication bpmTaskAssigneeApplication;

    @Resource
    @Lazy
    private IBpmProcessInstanceApplication bpmProcessInstanceApplication;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void complete(BpmTaskReqVO taskVo) {
        String userId = StringUtils.isEmpty(taskVo.getUserId()) ? SecurityUtils.getUserCode() : taskVo.getUserId();
        // 校验任务存在
        Task task = bpmPermissionService.checkTask(taskVo.getTaskId(), userId);
        String taskId = task.getId();
        String processInstanceId = task.getProcessInstanceId();
        Map<String, Object> completeVariables = Objects.nonNull(taskVo.getProcessVariables()) ? taskVo.getProcessVariables() : new HashMap<>();
        // 校验流程实例存在
        ProcessInstance instance = bpmProcessInstanceService.getProcessInstance(processInstanceId);
        Optional.ofNullable(instance).orElseThrow(() -> exception(PROCESS_INSTANCE_NOT_EXISTS));

        Map<String, Object> taskLocalVariables = Optional.ofNullable(taskVo.getTaskLocalVariables()).orElse(new HashMap<>());
        if (StringUtils.isNotEmpty(taskVo.getMultiResult())) {
            taskLocalVariables.put(BpmConstants.MULTI_RESULT, taskVo.getMultiResult());
        }

        if (!taskLocalVariables.isEmpty()) {
            taskService.setVariablesLocal(taskId, taskLocalVariables);
        }

        if (StringUtils.isEmpty(task.getAssignee()) || (StringUtils.isNotEmpty(userId) && !userId.equals(task.getAssignee()))) {
            // 忽略委托
            taskService.setVariableLocal(task.getId(), BpmConstants.ENTRUST_IGNORE, BpmConstants.ENTRUST_IGNORE);
            taskService.setAssignee(taskId, userId);
        }

        // 委派完成
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            String userName = bpmTaskAssigneeApplication.getUserNameByUserCode(task.getOwner());

            // 产生已办、活动历史记录
            managementService.executeCommand(new CreateHistoricDataCmd(taskId, userName, taskVo.getComment(), BpmExecuteCmd.RESOLVE));

            //设置标识
            completeVariables.put(BpmConstants.EXECUTE_CMD, BpmExecuteCmd.RESOLVE.cmd);
            // taskService.setVariableLocal(taskId, BpmConstants.EXECUTE_CMD, BpmExecuteCmd.RESOLVE.cmd);
            taskService.resolveTask(taskId, completeVariables);

            //重置任务创建时间为当前时间
            bpmTaskService.resetTaskCreateTime(taskId);
        }
        // 完成
        else {
            BpmExecuteCmd exCmd = Optional.ofNullable(taskVo.getExecuteCmd()).map(e -> BpmExecuteCmd.getBpmExecute(taskVo.getExecuteCmd())).orElse(BpmExecuteCmd.APPROVE);
            bpmCommentService.addComment(taskId, processInstanceId, taskVo.getComment(), exCmd, null);
            //设置标识
            completeVariables.put(BpmConstants.EXECUTE_CMD, exCmd.cmd);
            //标识当前处理人
            completeVariables.put(BpmConstants.USER_ID,task.getAssignee());
            managementService.executeCommand(new SetTaskScopeTypeCmd(taskId, exCmd.cmd));
            // 变量传递会设置成全局变量
            taskService.complete(taskId, completeVariables);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackTaskByActivityIds(BpmTaskRejectReqVO taskVo) {
        Optional.ofNullable(taskVo.getSourceKeyList())
                .orElseThrow(() -> exception(TASK_SOURCE_IS_EMPTY_KEYS));

        Task task = bpmPermissionService.checkTask(taskVo.getTaskId());
        bpmCommentService.addComment(task.getId(), task.getProcessInstanceId(), taskVo.getComment(), BpmExecuteCmd.BACK, null);
        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.BACK.cmd);

        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(task.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(taskVo.getSourceKeyList(), taskVo.getTargetKey())
                .changeState();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackTaskByTaskId(BpmTaskRejectReqVO taskVo) {
        Task task = bpmPermissionService.checkTask(taskVo.getTaskId());
        String taskId = task.getId();
        String userCode = SecurityUtils.getUserCode();

        List<Execution> list = bpmExecutionService.getExecutionByParentId(task.getProcessInstanceId());
        Optional.ofNullable(list).orElseThrow(() -> exception(TASK_EXECUTION_IS_EMPTY));
        List<String> executionIds = new ArrayList<>();
        list.forEach(e -> executionIds.add(e.getId()));

        BpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        Activity activity = FlowableUtil.getElementInfo(bpmnModel, taskVo.getTargetKey());
        String targetName = activity.getName();

        bpmCommentService.addComment(taskId, task.getProcessInstanceId(), taskVo.getComment(), BpmExecuteCmd.BACK, targetName);
        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.BACK.cmd);
        managementService.executeCommand(new SetTaskScopeTypeCmd(taskId, BpmExecuteCmd.BACK.cmd));

        if (StringUtils.isEmpty(task.getAssignee()) || (StringUtils.isNotEmpty(userCode) && !userCode.equals(task.getAssignee()))) {
            // 忽略委托
            taskService.setVariableLocal(taskId, BpmConstants.ENTRUST_IGNORE, BpmConstants.ENTRUST_IGNORE);
            taskService.setAssignee(taskId, userCode);
        }
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(task.getProcessInstanceId())
                .moveExecutionsToSingleActivityId(executionIds, taskVo.getTargetKey())
                .changeState();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void claim(BpmTaskReqVO reqVO) {
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        String user = reqVO.getAssignee();
        if (StringUtils.isEmpty(user)) {
            user = SecurityUtils.getUserCode();
        }
        if (Objects.equals(user, task.getAssignee())) {
            return;
        }

        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.CLAIM.cmd);
        managementService.executeCommand(new SetTaskScopeTypeCmd(task.getId(), BpmExecuteCmd.CLAIM.cmd));
        try {
            taskService.setVariableLocal(task.getId(), BpmConstants.ENTRUST_IGNORE, BpmConstants.ENTRUST_IGNORE);
            taskService.claim(task.getId(), user);
        } catch (FlowableTaskAlreadyClaimedException e) {
            log.error("任务已被领取：{}", task.getId());
            throw exception(TASK_ALREADY_CLAIMED);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unClaim(BpmTaskReqVO reqVO) {
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.UN_CLAIM.cmd);
        taskService.setVariableLocal(task.getId(), BpmConstants.ENTRUST_IGNORE, BpmConstants.ENTRUST_IGNORE);
        managementService.executeCommand(new SetTaskScopeTypeCmd(task.getId(), BpmExecuteCmd.UN_CLAIM.cmd));
        taskService.unclaim(task.getId());

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delegate(BpmTaskUpdateAssigneeReqVO reqVO) {
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        if (Objects.equals(DelegationState.PENDING, task.getDelegationState())) {
            throw exception(TASK_ALREADY_DELEGATE);
        }
        String taskId = task.getId();
        String userName = bpmTaskAssigneeApplication.getUserNameByUserCode(reqVO.getAssignee());

        // 产生已办、活动历史记录
        managementService.executeCommand(new CreateHistoricDataCmd(taskId, userName, reqVO.getComment(), BpmExecuteCmd.DELEGATE));


        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.DELEGATE.cmd);
        managementService.executeCommand(new SetTaskScopeTypeCmd(task.getId(), BpmExecuteCmd.DELEGATE.cmd));
        taskService.delegateTask(taskId, reqVO.getAssignee());

        //重置任务创建时间为当前时间
        bpmTaskService.resetTaskCreateTime(taskId);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignTask(BpmTaskUpdateAssigneeReqVO reqVO) {
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        String taskId = task.getId();
        String userName = bpmTaskAssigneeApplication.getUserNameByUserCode(reqVO.getAssignee());

        // 先释放
        Optional.ofNullable(task.getClaimTime()).ifPresent(t -> taskService.unclaim(taskId));
        managementService.executeCommand(new ClearTaskDescCmd(taskId, BpmTaskCategory.CANDIDATE.getCode()));

        //设置标识
        runtimeService.setVariable(task.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.ASSIGN.cmd);
        managementService.executeCommand(new SetTaskScopeTypeCmd(task.getId(), BpmExecuteCmd.ASSIGN.cmd));

        //原来的任务转办 不增加记录
        taskService.setAssignee(taskId, reqVO.getAssignee());

        // 产生已办、活动历史记录
        managementService.executeCommand(new CreateHistoricDataCmd(taskId, userName, reqVO.getComment(), BpmExecuteCmd.ASSIGN));

        //重置任务创建时间为当前时间
        bpmTaskService.resetTaskCreateTime(taskId);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void stopTask(BpmTaskReqVO reqVO) {
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        managementService.executeCommand(new SetTaskScopeTypeCmd(task.getId(), BpmExecuteCmd.STOP.cmd));
        bpmProcessInstanceApplication.cancelProcessInstance(new BpmProcessInstanceCancelReqVO(task.getProcessInstanceId(), reqVO.getComment()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMultiInstanceExecution(BpmAddMultiExtReqVO reqVo) {
        runtimeService.addMultiInstanceExecution(reqVo.getActivityId(),
                reqVo.getProcessInstanceId(),
                reqVo.getExecutionVariables());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCandidateUser(BpmTaskReqVO reqVO) {
        List<String> candidateUsers = reqVO.getCandidateUserList();
        Optional.ofNullable(candidateUsers)
                .orElseThrow(() -> exception(TASK_IS_EMPTY_CANDIDATE_USER));

        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());

        candidateUsers.forEach(u -> {
            Optional.ofNullable(u).ifPresent(i -> {
                taskService.addCandidateUser(task.getId(), i);
            });
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCandidateGroup(BpmTaskReqVO reqVO) {
        List<String> candidateGroups = reqVO.getCandidateGroupList();
        Optional.ofNullable(candidateGroups)
                .orElseThrow(() -> exception(TASK_IS_EMPTY_CANDIDATE_GROUP));
        Task task = bpmPermissionService.checkTask(reqVO.getTaskId());
        candidateGroups.forEach(g -> {
            Optional.ofNullable(g)
                    .ifPresent(i -> taskService.addCandidateGroup(task.getId(), i));
        });
    }

    @Override
    public Boolean canWithdrawTask(String taskId) {
        HistoricTaskInstance hisTask = bpmTaskService.getHistTaskById(taskId);
        Optional.ofNullable(hisTask)
                .orElseThrow(() -> exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS));

        //流程已经结束
        if (bpmProcessInstanceService.isProcessFinished(hisTask.getProcessInstanceId())) {
            throw exception(TASK_WITHDRAW_FAIL_PROCESS_FINISHED);
        }
        // 当前节点处于callActivity 且未完成 处于子流程中
        if (!bpmActivityService.isCallActivityFinished(hisTask.getProcessInstanceId())) {
            throw exception(TASK_WITHDRAW_FAIL_CALL_ACTIVITY_UNFINISHED);
        }
        // 会签节点不让撤回
        BpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModelByDefinitionId(hisTask.getProcessDefinitionId());
        if (FlowableUtil.isTaskMulti(bpmnModel, hisTask.getTaskDefinitionKey())) {
            throw exception(TASK_WITHDRAW_FAIL_TASK_IS_MULTI);
        }

        // 撤回的节点 有运行任务
        long count = taskService.createTaskQuery()
                .processInstanceId(hisTask.getProcessInstanceId())
                .taskDefinitionKey(hisTask.getTaskDefinitionKey())
                .count();
        if (count > 0) {
            throw exception(TASK_WITHDRAW_FAIL_TASK_IS_CURRENT);
        }

        // 未考虑内嵌子流程情况

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawTask(BpmTaskReqVO reqVO) {
        managementService.executeCommand(new WithdrawTaskCmd(reqVO.getTaskId(), reqVO.getComment(), reqVO.getBusinessKey()));

        /*if (canWithdrawTask(reqVO.getTaskId())) {
            HistoricTaskInstance hisTask = bpmTaskService.getHistTaskById(reqVO.getTaskId());
            List<Execution> list = bpmExecutionService.getExecutionByParentId(hisTask.getProcessInstanceId());
            Optional.ofNullable(list)
                    .orElseThrow(() -> exception(TASK_WITHDRAW_FAIL_EXECUTION_IS_EMPTY));

            List<String> executionIds = new ArrayList<>();
            String userCode = SecurityUtils.getUserCode();
            list.forEach(e -> {
                executionIds.add(e.getId());
                Task task = bpmTaskService.getTaskByExecutionId(e.getId());
                // 被撤回任务设置归属人
                Optional.ofNullable(task.getAssignee()).ifPresent(u -> taskService.setOwner(task.getId(), u));
                // 被撤回的任务设置 处理人为当前人
                taskService.setAssignee(task.getId(), userCode);

                // 被撤回的任务增加 意见
                bpmCommentService.addComment(task.getId(), task.getProcessInstanceId(), reqVO.getComment(), BpmExecuteCmd.WITHDRAW, hisTask.getName());
            });
            //设置标识
            runtimeService.setVariable(hisTask.getProcessInstanceId(), BpmConstants.EXECUTE_CMD, BpmExecuteCmd.WITHDRAW.cmd);
            runtimeService.setVariable(hisTask.getProcessInstanceId(), BpmConstants.WITHDRAW_USER, userCode);

            runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(hisTask.getProcessInstanceId())
                    .moveExecutionsToSingleActivityId(executionIds,
                            hisTask.getTaskDefinitionKey()).changeState();

            // 如果是同步 查询新生成的任务 设置审批人为当前人
            Task task = bpmTaskService.getTaskByProcessInstanceId(hisTask.getProcessInstanceId());
            Optional.ofNullable(task).ifPresent(t -> t.setAssignee(userCode));
        }*/
    }

    @Override
    public List<BpmTaskDTO> getMainProcessHisTasksByBusinessKey(String businessKey) {
        HistoricProcessInstance instance = bpmProcessInstanceService.getMainProcessInstanceByBusinessKey(businessKey);
        if (Objects.isNull(instance)) {
            return Collections.emptyList();
        }
        List<HistoricTaskInstance> hisList = bpmTaskService.getTasksFinishedByProcessInstanceId(instance.getId());

        return BpmTaskConvert.INSTANCE.convert4(hisList);
    }

    @Override
    public Map<String, Set<String>> getSubProcessOnAssignee(Set<String> ids) {
        List<Task> list = bpmTaskService.getTasksByProcessInstanceIds(ids);

        return convertMultiMap2(list, Task::getProcessInstanceId, Task::getAssignee);
    }

    @Override
    public List<BpmTaskDTO> getRunMultiTaskInfo(String taskId) {
        Task task = bpmTaskService.getTaskById(taskId);
        Optional.ofNullable(task).orElseThrow(() -> exception(TASK_EXECUTION_IS_EMPTY));
        List<Execution> executionList = runtimeService.createExecutionQuery()
                .processInstanceId(task.getProcessInstanceId()).list();
        // 当前任务 执行数据
        Execution execution = findFirst(executionList, e -> Objects.equals(e.getId(), task.getExecutionId()));
        if (Objects.isNull(execution)) {
            return Collections.emptyList();
        }
        // 当前节点的任务执行数据
        List<Execution> executions = filterList(executionList, e -> Objects.equals(e.getParentId(), execution.getParentId()) && Objects.equals(e.getActivityId(), task.getTaskDefinitionKey()));
        Set<String> ids = convertSet(executions, Execution::getId);
        // 同节点的任务-可能包括退回重新运行的任务
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .includeTaskLocalVariables()
                .taskDefinitionKey(task.getTaskDefinitionKey()).list();

        // 过滤本次运行的会签点数据 不包括本次 转办 委派的数据
        List<HistoricTaskInstance> filterList = filterList(list, t -> ids.contains(t.getExecutionId())
                && Optional.ofNullable(t.getDescription()).map(d -> !d.contains(BpmExecuteCmd.ASSIGN.cmd)).orElse(true)
                && Optional.ofNullable(t.getDescription()).map(d -> !d.contains(BpmExecuteCmd.DELEGATE.cmd)).orElse(true));
        // List<HistoricTaskInstance> list1 = bpmHistoricTaskMapper.selectMultiHistoricTaskByExecutionIds(ids);
        // list1.get(0).getTaskLocalVariables();

        return BpmTaskConvert.INSTANCE.convert4(filterList);
    }

    @Override
    public BpmTaskDTO getRecentlyTask(String processInstanceId) {
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime().desc().list();
        if (CollUtil.isEmpty(list)) {
            return null;
        }

        return BpmTaskConvert.INSTANCE.convert4(list.get(0));
    }

    @Override
    public String getEntrustOwner(String taskId) {
        Object local = bpmTaskService.getVariableLocal(taskId, BpmConstants.ENTRUST_OWNER);

        return Optional.ofNullable(local)
                .map(o -> String.valueOf(local))
                .orElse(null);
    }



}
